package main

import (
	"context"
	"fmt"
)

type handler func(context.Context)

type invoker func(context.Context, []interceptor, handler) error

type interceptor func(context.Context, handler, invoker) error

func main() {
	ctx := context.TODO()
	var ceps []interceptor
	var h = func(ctx context.Context) {
		fmt.Println("do something")
	}

	var inter1 = func(ctx context.Context, h handler, ivk invoker) error {
		h(ctx)
		fmt.Println("interceptor1")
		return ivk(ctx, ceps, h)
	}
	var inter2 = func(ctx context.Context, h handler, ivk invoker) error {
		h(ctx)
		fmt.Println("interceptor2")
		return ivk(ctx, ceps, h)
	}

	var inter3 = func(ctx context.Context, h handler, ivk invoker) error {
		h(ctx)
		fmt.Println("interceptor3")
		return ivk(ctx, ceps, h)
	}

	ceps = append(ceps, inter1, inter2, inter3)

	var ivk = func(ctx context.Context, interceptors []interceptor, h handler) error {
		fmt.Println("invoker start")
		return nil
	}

	cep := getChainInterceptor(ctx, ceps, ivk)

	err := cep(ctx, h, ivk)
	if err != nil {
		panic(err)
	}
}

func getInvoker(ctx context.Context, interceptors []interceptor, cur int, invoker invoker) invoker {
	if cur == len(interceptors)-1 {
		return invoker
	}
	return func(ctx context.Context, interceptors []interceptor, h handler) error {
		return interceptors[0](ctx, h, getInvoker(ctx, interceptors, cur+1, invoker))
	}
}

func getChainInterceptor(ctx context.Context, interceptors []interceptor, ivk invoker) interceptor {
	if len(interceptors) == 0 {
		return nil
	}
	if len(interceptors) == 1 {
		return interceptors[0]
	}

	return func(context context.Context, h handler, i invoker) error {
		return interceptors[0](context, h, getInvoker(context, interceptors, 0, i))
	}
}
